/*
 * AS3 MultiTouch Client
 *
 * TouchManager - Manages Touchables.
 *
 * Copyright 2008 Artifacts - Fine Software Development
 * http://www.artifacts.de
 * Author: Michael Markowski (m.markowski@artifacts.de)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
 
/**
 * TouchManager - Manages Touchables.
 * The TouchManager is the central controller for controlling touchables.
 * Arbitrary DisplayObjects can be made touchable by calling makeTouchable()
 * with the object as parameter. Use addBehaviour() to register object-specific
 * behaviour when touched, e.g. a DragBehaviour to make the object draggable.
*/
package de.artifacts.multitouch
{
	import de.artifacts.multitouch.behaviours.TouchBehaviour;
	
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.*;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	
	import mx.core.Application;
	
	public class TouchManager extends EventDispatcher
	{
		public static const kPositionChangedTreshold : int = 10;

		public static const cursorWidth : Number = 10;
		public static const cursorHeight : Number = 10;

		private var provider : ITouchEventProvider;
		public var touchables : Dictionary;
		public var touchArea : Sprite;
		public var cursors : Dictionary;
		public var showFingerprint : Boolean = false;
		public var selectedTouchables : Array;
		public var emulatedMouseX : int;
		public var emulatedMouseY : int;
		public var simulateMouse : Boolean = false;		
		public var lastTouchTime : int;
		
		public function TouchManager(touchArea : Sprite, provider : ITouchEventProvider)		
		{
			this.provider = provider;
			this.provider.setTouchManager(this);
			this.cursors = new Dictionary();
			this.touchArea = touchArea;						
			this.touchables = new Dictionary();
		}

		public function makeTouchable(obj : DisplayObject, zIndex : int, nonExclusive : Boolean = false) : void
		{
			this.touchables[obj] = new Touchable(obj, this, zIndex, nonExclusive);					
		}
		
		public function makeUntouchable(obj : DisplayObject) : void
		{
			this.touchables[obj] = null;
			delete touchables[obj];
		}
		
		public function addBehaviour(obj : DisplayObject, behaviour : TouchBehaviour) : void
		{
			behaviour.touchManager = this;
			behaviour.touchable = (this.touchables[obj] as Touchable); 
			var touchable : Touchable = (this.touchables[obj] as Touchable);
			if (touchable != null)
			{			
				touchable.behaviours.push(behaviour);
			}
			else
			{
				throw new Error("Failed to add behaviour to non-touchable object.");
			}
		}
			
		public function cursorMoved(cursor : Cursor) : void
		{			
			var event : TouchEvent = new TouchEvent(TouchEvent.REPOSITION);
			event.cursor = cursor; 			
			for each (var t : Touchable in cursor.touchables) {
				for each (var b : TouchBehaviour in t.behaviours) {
					b.repositionedHandler(event);					
				}
			}
			if (showFingerprint) cursor.drawFingerprint();			
			if (simulateMouse)
			{
				var evt : MouseEvent = new MouseEvent(MouseEvent.MOUSE_MOVE); 
				Application.application.dispatchEvent(evt);			
				emulatedMouseX = cursor.position.x;
				emulatedMouseY = cursor.position.y;
			}				
		}
		
		/* Are we touched exclusively by the given cursor ? */
		public function isExclusiveTouch(theTouchable : Touchable, theCursor : Cursor) : Boolean
		{
			// find out, if we are touched by more than one cursors.
			for each (var aCursor : Cursor in this.cursors)
			{
				if (aCursor==theCursor) continue;
				if (aCursor.touchables.indexOf(theTouchable)>-1) return false;
			}
			return true;
		}		
		
		public function cursorDown(x : Number, y : Number, id : int, tapNumber : int) : void
		{			
			var currentCursor : Cursor = this.cursors[id]
			if (currentCursor==null) return; // cursor MUST exist, since we have created it in createCursor()
			currentCursor.position.x = x;
			currentCursor.position.y = y;
			// add touchables touched by this cursor to cursor
			var touchablesArray : Array = new Array();
			for each (touchable in touchables) touchablesArray.push(touchable);
			touchablesArray.sortOn("zIndex", Array.NUMERIC | Array.DESCENDING);
			for (var i : int = 0; i < touchablesArray.length; i++)			
			{
				var touchable : Touchable = touchablesArray[i];				
				if (currentCursor.hits(touchable)) 
				{				
					if (isExclusiveTouch(touchable, currentCursor)) calculateCursorOffset(currentCursor, touchable);					
					currentCursor.touchables.push(touchable);
					touchable.touchingCursors.push(currentCursor);
					var event : TouchEvent = new TouchEvent(TouchEvent.DOWN);				
					event.cursor = currentCursor;  
					this.dispatchEvent(event);
					for each (var t : Touchable in currentCursor.touchables) {
						for each (var b : TouchBehaviour in t.behaviours) {
							b.touchedHandler(event);					
						}
					}
					if (!touchable.isNonExclusive) 
					{
						return;
					}
				}
			}						
		}
		
		private function calculateCursorOffset(cursor : Cursor, touchable : Touchable) : void
		{
			touchable.positionOnFirstTouch = new Point(cursor.position.x, cursor.position.y);
			touchable.offsetX = cursor.position.x - touchable.wrappee.x;
			touchable.offsetY = cursor.position.y - touchable.wrappee.y;
		}
		
		/**
		 * Liefert true, wenn sich die Position des Objekts (Touchables) seit dem Erzeugen des Cursors
		 * verändert hat. Dabei wird der übergebene Schwellenwert berücksichtigt.
		 */
		public function isPositionChanged(touchable : Touchable) : Boolean
		{
			var initialPositionRect : Rectangle  = new Rectangle(touchable.positionOnFirstTouch.x, 
																 touchable.positionOnFirstTouch.y, 
																 kPositionChangedTreshold, 
																 kPositionChangedTreshold); 			
			var currentRect : Rectangle = new Rectangle(touchable.wrappee.x + touchable.offsetX, 
														touchable.wrappee.y + touchable.offsetY, 
														kPositionChangedTreshold, 
														kPositionChangedTreshold);
			var intersects : Boolean = currentRect.intersects(initialPositionRect);
			return !intersects;			 
		}
		
		
		public function cursorUp(x : Number, y : Number, id : int) : void
		{
			var event : TouchEvent = new TouchEvent(TouchEvent.UP);
			var currentCursor : Cursor = this.cursors[id];
			event.cursor = currentCursor;
			event.cursorId = new String(id);
			if (currentCursor==null || currentCursor.touchables==null) return;
			
			for each (var t : Touchable in currentCursor.touchables) {
				var otherCursor : Cursor = getOtherCursor(t, currentCursor);
				if (otherCursor != null) calculateCursorOffset(otherCursor, t);
				for each (var b : TouchBehaviour in t.behaviours) {
					b.releasedHandler(event);
				}
			}
		}
		
		private function getOtherCursor(touchable : Touchable, cursor : Cursor) : Cursor
		{
			for (var i : int = 0; i<touchable.touchingCursors.length; i++)
			{
				if (touchable.touchingCursors[i].id!=cursor.id) return touchable.touchingCursors[i]; 
			}
			return null;
		}
		
		public function createCursor(id : int) : Cursor
		{
			var touchable : DisplayObject;
			var currentCursor : Cursor = this.cursors[id];
			if (currentCursor==null)
			{
				// new cursor
				currentCursor = new Cursor(-1, -1, cursorWidth, cursorHeight, id);
				this.cursors[id] = currentCursor;
				touchArea.addChild(currentCursor);
			}
			var event : TouchEvent = new TouchEvent(TouchEvent.CREATE);
			event.cursor = currentCursor;
			return currentCursor;			
		}

		public function removeCursor(id : int) : void
		{			
			if (this.cursors[id]!=null)
			{
				var currentCursor : Cursor = this.cursors[id];
				for each (var aTouchable : Touchable in currentCursor.touchables) {
					aTouchable.touchingCursors.splice(aTouchable.touchingCursors.indexOf(currentCursor), 1);
				}
				touchArea.removeChild(this.cursors[id]);
				this.cursors[id]=null;
				delete this.cursors[id]; 
			}
			var event : TouchEvent = new TouchEvent(TouchEvent.DESTROY);
			event.cursor = cursors[id];
		}
		
		public function firstHittingCursor(touchable : Touchable) : Cursor
		{			
			var cursor : Cursor;
			for each (cursor in cursors)
			{				
				if (cursor.hits(touchable)) return cursor;
			}
			return null;			
		}
		
	}
}